/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.aspectj.annotation;

import org.aopalliance.aop.Advice;
import org.aspectj.lang.annotation.*;
import org.springframework.aop.Advisor;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.aspectj.*;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConvertingComparator;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.comparator.CompoundComparator;
import org.springframework.util.comparator.InstanceComparator;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

/**
 * Factory that can create Spring AOP Advisors given AspectJ classes from
 * classes honoring the AspectJ 5 annotation syntax, using reflection to
 * invoke the corresponding advice methods.
 *
 * @author Rod Johnson
 * @author Adrian Colyer
 * @author Juergen Hoeller
 * @author Ramnivas Laddad
 * @author Phillip Webb
 * @since 2.0
 */
public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory {

    private static final Comparator<Method> METHOD_COMPARATOR;

    static {
        CompoundComparator<Method> comparator = new CompoundComparator<Method>();
        comparator.addComparator(new ConvertingComparator<Method, Annotation>(
                new InstanceComparator<Annotation>(
                        Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
                new Converter<Method, Annotation>() {
                    public Annotation convert(Method method) {
                        AspectJAnnotation<?> annotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
                        return annotation == null ? null : annotation.getAnnotation();
                    }
                }));
        comparator.addComparator(new ConvertingComparator<Method, String>(
                new Converter<Method, String>() {
                    public String convert(Method method) {
                        return method.getName();
                    }
                }));
        METHOD_COMPARATOR = comparator;
    }


    public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
        //从 aspectMetadata 中获取 Aspect()标注的类 class对象
        final Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass();
        //获取Aspect()标注的类名
        final String aspectName = maaif.getAspectMetadata().getAspectName();
        validate(aspectClass);

        // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
        // so that it will only instantiate once.
        final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
                new LazySingletonAspectInstanceFactoryDecorator(maaif);

        final List<Advisor> advisors = new LinkedList<Advisor>();
        //遍历该类所有方法，根据方法判断是否能获取到对应 pointCut，如果有，则生成 advisor 对象
        for (Method method : getAdvisorMethods(aspectClass)) {
            Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }

        // If it's a per target aspect, emit the dummy instantiating aspect.
        if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
            Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
            advisors.add(0, instantiationAdvisor);
        }

        // Find introduction fields.
        //获取 @DeclareParents 注解修饰的属性（并不常用）
        for (Field field : aspectClass.getDeclaredFields()) {
            Advisor advisor = getDeclareParentsAdvisor(field);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }

        return advisors;
    }

    private List<Method> getAdvisorMethods(Class<?> aspectClass) {
        final List<Method> methods = new LinkedList<Method>();
        ReflectionUtils.doWithMethods(aspectClass, new ReflectionUtils.MethodCallback() {
            public void doWith(Method method) throws IllegalArgumentException {
                // Exclude pointcuts
                if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
                    methods.add(method);
                }
            }
        });
        Collections.sort(methods, METHOD_COMPARATOR);
        return methods;
    }

    /**
     * Build a {@link org.springframework.aop.aspectj.DeclareParentsAdvisor}
     * for the given introduction field.
     * <p>Resulting Advisors will need to be evaluated for targets.
     *
     * @param introductionField the field to introspect
     * @return {@code null} if not an Advisor
     */
    private Advisor getDeclareParentsAdvisor(Field introductionField) {
        DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class);
        if (declareParents == null) {
            // Not an introduction field
            return null;
        }

        if (DeclareParents.class.equals(declareParents.defaultImpl())) {
            // This is what comes back if it wasn't set. This seems bizarre...
            // TODO this restriction possibly should be relaxed
            throw new IllegalStateException("defaultImpl must be set on DeclareParents");
        }

        return new DeclareParentsAdvisor(
                introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
    }


    public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif,
                              int declarationOrderInAspect, String aspectName) {

        validate(aif.getAspectMetadata().getAspectClass());

        //根据候选方法名，来获取对应的 pointCut
        AspectJExpressionPointcut ajexp =
                getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass());
        if (ajexp == null) {
            return null;
        }
        //如果能获取到 pointCut，则将切点表达式 expressionPointcut、
        //当前对象ReflectiveAspectJAdvisorFactory、 方法名等包装成 advisor 对象
        return new InstantiationModelAwarePointcutAdvisorImpl(
                this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName);
    }

    private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
        AspectJAnnotation<?> aspectJAnnotation =
                AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
        if (aspectJAnnotation == null) {
            return null;
        }
        AspectJExpressionPointcut ajexp =
                new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
        ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
        return ajexp;
    }


    public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp,
                            MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) {


        Class<?> candidateAspectClass = aif.getAspectMetadata().getAspectClass();
        validate(candidateAspectClass);

        AspectJAnnotation<?> aspectJAnnotation =
                AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
        if (aspectJAnnotation == null) {
            return null;
        }

        // If we get here, we know we have an AspectJ method.
        // Check that it's an AspectJ-annotated class
        if (!isAspect(candidateAspectClass)) {
            throw new AopConfigException("Advice must be declared inside an aspect type: " +
                    "Offending method '" + candidateAdviceMethod + "' in class [" +
                    candidateAspectClass.getName() + "]");
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Found AspectJ method: " + candidateAdviceMethod);
        }

        AbstractAspectJAdvice springAdvice;
        //......
        //根据注解类型，匹配对应的通知类型
        switch (aspectJAnnotation.getAnnotationType()) {
            //前置通知
            case AtBefore:
                springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
                break;
            //(最终)执行完成 通知
            case AtAfter:
                springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
                break;
            //return 后置通知
            case AtAfterReturning:
                springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
                AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                    springAdvice.setReturningName(afterReturningAnnotation.returning());
                }
                break;
            //异常通知
            case AtAfterThrowing:
                springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
                AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                    springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
                }
                break;
            //环绕通知
            case AtAround:
                springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
                break;
            //切面
            case AtPointcut:
                if (logger.isDebugEnabled()) {
                    logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
                }
                return null;
            default:
                throw new UnsupportedOperationException(
                        "Unsupported advice type on method " + candidateAdviceMethod);
        }
        //......

        // Now to configure the advice...
        springAdvice.setAspectName(aspectName);
        springAdvice.setDeclarationOrder(declarationOrderInAspect);
        String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
        if (argNames != null) {
            springAdvice.setArgumentNamesFromStringArray(argNames);
        }
        springAdvice.calculateArgumentBindings();
        return springAdvice;
    }

    /**
     * Synthetic advisor that instantiates the aspect.
     * Triggered by per-clause pointcut on non-singleton aspect.
     * The advice has no effect.
     */
    @SuppressWarnings("serial")
    protected static class SyntheticInstantiationAdvisor extends DefaultPointcutAdvisor {

        public SyntheticInstantiationAdvisor(final MetadataAwareAspectInstanceFactory aif) {
            super(aif.getAspectMetadata().getPerClausePointcut(), new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, Object target) {
                    // Simply instantiate the aspect
                    aif.getAspectInstance();
                }
            });
        }
    }

}
